package xlog

import (
	"context"
	"errors"
	"gopkg.in/natefinch/lumberjack.v2"
	"io"
	"log/slog"
	"os"
	"strings"
)

type MultiHandler struct {
	handlers    []slog.Handler
	errHandlers []slog.Handler
}

func NewMultiHandler(config *Config) (*MultiHandler, error) {
	mHandler := &MultiHandler{}
	opts := &slog.HandlerOptions{
		Level:       config.Level,
		AddSource:   config.AddSource,
		ReplaceAttr: config.ReplaceAttr,
	}
	handlers, err := newMultiHandlers(config.OutputPaths, config.FileMaxSize, config.BackUpFileMaxAge, config.Format, nil, opts)
	if err != nil {
		return nil, err
	}
	mHandler.handlers = handlers

	if len(config.ErrPaths) == 0 {
		return mHandler, nil
	}

	errOpts := &slog.HandlerOptions{
		Level:       slog.LevelError,
		AddSource:   config.AddSource,
		ReplaceAttr: config.ReplaceAttr,
	}
	errHandlers, err := newMultiHandlers(config.ErrPaths, config.FileMaxSize, config.BackUpFileMaxAge, config.Format, nil, errOpts)
	if err != nil {
		return nil, err
	}
	mHandler.errHandlers = errHandlers
	return mHandler, nil
}

func newMultiHandlers(paths []string, fileMaxSize, backUpMaxAge int, format string, fields map[string]any, opts *slog.HandlerOptions) ([]slog.Handler, error) {
	var err error
	var handlers = make([]slog.Handler, 0)
	for _, p := range paths {
		if strings.ToLower(p) == "stdout" {
			handlers = append(handlers, newHandler(format, fields, os.Stdout, opts))
		} else if strings.ToLower(p) == "stderr" {
			handlers = append(handlers, newHandler(format, fields, os.Stderr, opts))
		} else {
			writer := getFileWriter(p, fileMaxSize, backUpMaxAge)
			handlers = append(handlers, newHandler(format, fields, writer, opts))
		}
	}

	return handlers, err
}

func getFileWriter(filename string, fileMaxSize, backUpMaxAge int) io.Writer {
	if fileMaxSize < 1 {
		fileMaxSize = 100
	}
	if backUpMaxAge < 1 {
		backUpMaxAge = 7
	}

	lumberJackLogger := &lumberjack.Logger{
		Filename:  filename,
		MaxSize:   fileMaxSize,
		MaxAge:    backUpMaxAge,
		LocalTime: false,
		Compress:  false,
	}
	return lumberJackLogger
}

func newHandler(format string, fields map[string]any, w io.Writer, opts *slog.HandlerOptions) slog.Handler {
	var handler slog.Handler
	if format == JsonFormat {
		handler = slog.NewJSONHandler(w, opts)
	} else {
		handler = slog.NewTextHandler(w, opts)
	}

	if len(fields) > 0 {
		attr := make([]slog.Attr, 0)
		for k, v := range fields {
			attr = append(attr, slog.Any(k, v))
		}
		handler = handler.WithAttrs(attr)
	}

	return handler
}

func (h *MultiHandler) Enabled(ctx context.Context, level slog.Level) bool {
	return h.handlers[0].Enabled(ctx, level)
}

func (h *MultiHandler) Handle(ctx context.Context, record slog.Record) error {
	var err error
	for _, v := range h.handlers {
		if e := v.Handle(ctx, record); e != nil {
			err = errors.Join(e)
		}
	}

	if record.Level >= slog.LevelError {
		for _, v := range h.errHandlers {
			if e := v.Handle(ctx, record); e != nil {
				err = errors.Join(e)
			}
		}
	}

	return err
}

func (h *MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
	handlers := make([]slog.Handler, 0)
	errHandlers := make([]slog.Handler, 0)
	for _, v := range h.handlers {
		handlers = append(handlers, v.WithAttrs(attrs))
	}

	for _, v := range h.errHandlers {
		errHandlers = append(errHandlers, v.WithAttrs(attrs))
	}

	return &MultiHandler{handlers: handlers, errHandlers: errHandlers}
}

func (h *MultiHandler) WithGroup(name string) slog.Handler {
	handlers := make([]slog.Handler, 0)
	errHandlers := make([]slog.Handler, 0)
	for _, v := range h.handlers {
		handlers = append(handlers, v.WithGroup(name))
	}

	for _, v := range h.errHandlers {
		errHandlers = append(errHandlers, v.WithGroup(name))
	}

	return &MultiHandler{handlers: handlers, errHandlers: errHandlers}
}
